#include "main_widget.h"
#include "ui_main_widget.h"
#include <QDebug>
#include <QMessageBox>
#include <QFileDialog>
#include <QProgressDialog>
#include <synchapi.h>
#include <QDialog>
#include <QtNetwork>


main_widget::main_widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::main_widget)
{
    ui->setupUi(this);

    tcp_client = new QTcpSocket(this);
    main_widget::g_frame_number = 0;
}

main_widget::~main_widget()
{
    delete ui;
}

// CRC查表值
const unsigned char auchCRCHi[]=
{
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
    0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
    0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
    0x40
};

const unsigned char auchCRCLo[] =
{
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
    0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
    0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
    0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
    0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
    0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
    0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
    0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
    0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
    0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
    0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
    0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
    0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
    0x40
};

/************************************************************************
函数名称: calc_crc16
函数功能: 计算N字节的Crc校验
函数参数: user_data: 需要计算CRC的数据
          user_data_len: 需要计算CRC的数据长度
函数返回: 计算得到的CRC
************************************************************************/
unsigned short main_widget::calc_crc16(const unsigned char *user_data,
                                       unsigned long user_data_len)
{
    unsigned char uchCRCHi = 0xFF;
    unsigned char uchCRCLo = 0xFF;
    unsigned short uindex = 0;
    unsigned short calc_crc = 0;

    while (user_data_len--)
    {
        uindex = uchCRCHi ^ (*user_data++);
        uchCRCHi = uchCRCLo ^ auchCRCHi[uindex];
        uchCRCLo = auchCRCLo[uindex];
    }

    calc_crc = static_cast<unsigned short>(uchCRCHi | (uchCRCLo << 8));

    return calc_crc;
}

/************************************************************************
函数名称: read_file
函数功能: 读取文件内容
函数参数: file_path: 文件路径
          position: 读取内容的起始位置
          len: 指定读取的长度
          read_buf: 读取到的文件内容
函数返回: 成功: 返回实际读取到的文件长度
          失败: 返回-1
************************************************************************/
qint64 main_widget::read_file(QString file_path, const unsigned long position,
            const unsigned long len, char *read_buf)
{
    bool ret = false;
    qint64 read_len = 0;

    // 以只读方式打开文件
    local_file = new QFile(file_path);
    ret = local_file->open(QFile::ReadOnly);
    if (false == ret)
    {
        qDebug() << "read_file open error";

        return -1;
    }

    ret = local_file->seek(position);       // 设置读取内容的起始位置
    if (false == ret)
    {
        qDebug("read_file fseek error");

        return -1;
    }

    read_len = local_file->read(read_buf, len);      // 读取文件内容
    if (-1 == read_len)
    {
        qDebug("read_file read error");

        return -1;
    }

    local_file->close();        // 关闭文件

    return read_len;
}

/************************************************************************
函数名称: recv_data_parsing
函数功能: 接收数据解析
函数参数: recv_data: 接收到的数据
          recv_data_len: 接收数据长度
          recv_data_type: 接收到的有效数据类型
函数返回: 成功: 返回0(RECV_OK)
          失败: 返回对应错误码(详见enum transfer_response)
************************************************************************/
int main_widget::recv_data_parsing(const unsigned char *recv_data, const unsigned long recv_data_len,
                unsigned char *recv_data_type)
{
    unsigned short calc_crc = 0;        // 根据上传数据计算得到的CRC
    unsigned short recv_crc = 0;        // 上传数据中的CRC
    unsigned long file_size = 0;        // 下发数据中获取到的文件大小
    struct file_transfer t_file_transfer;

    if ((0xCC == recv_data[0]) && (0x01 == recv_data[1]))
    {
        (*recv_data_type) = TRANSFER_FILE_NAME;        // 记录下发有效数据类型

        return RECV_OK;
    }
    else if ((0xCC == recv_data[0]) && (0x02 == recv_data[1]))
    {
        (*recv_data_type) = TRANSFER_FILE_SIZE;        // 记录下发有效数据类型

        return RECV_OK;
    }
    else if ((0xCC == recv_data[0]) && (0x03 == recv_data[1]))
    {
        (*recv_data_type) = TRANSFER_FILE_DATA;        // 记录下发有效数据类型

        return RECV_OK;
    }

    memset(&t_file_transfer, 0, sizeof(struct file_transfer));
    memcpy(&t_file_transfer, recv_data, recv_data_len);

    if (SERVER_HEAD != t_file_transfer.frame_head)      // 帧头错误
    {
        printf("t_file_transfer.frame_head = 0x%02X\n", t_file_transfer.frame_head);

        return HEAD_ERR;
    }

    // 获取上传数据中的crc
    recv_crc = static_cast<unsigned short>((recv_data[recv_data_len - 1] << 8) |
                                           recv_data[recv_data_len - 2]);
    calc_crc = calc_crc16(recv_data, (recv_data_len - 2));      // 计算本地crc
    if (recv_crc != calc_crc)       // CRC校验失败
    {
        return CRC_ERR;
    }

    // 判断数据类型
    switch (t_file_transfer.user_data_type)
    {
        case TRANSFER_FILE_NAME:        // 接收到文件名
        {
            (*recv_data_type) = TRANSFER_FILE_NAME;        // 记录下发有效数据类型

            // 下发的文件名正确
            if (0 == memcmp(t_file_info.file_name, t_file_transfer.user_data,
                            t_file_transfer.user_data_len))
            {
                // printf("file_name ok\n");
            }
            else
            {
                return OTHER_ERR;
            }

            break;
        }

        case TRANSFER_FILE_SIZE:        // 接收到文件大小
        {
            (*recv_data_type) = TRANSFER_FILE_SIZE;        // 记录下发有效数据类型

            // 获取下发数据中的文件大小
            file_size = (static_cast<unsigned int>
                         (t_file_transfer.user_data[3] << 24) & 0xFF000000) |
                         ((t_file_transfer.user_data[2] << 16) & 0x00FF0000) |
                         ((t_file_transfer.user_data[1] << 8) & 0x0000FF00) |
                         ((t_file_transfer.user_data[0] << 0) & 0x000000FF);

            // 下发的文件大小正确
            if (t_file_info.file_size == file_size)
            {
                // printf("file_size ok\n");
            }
            else
            {
                return OTHER_ERR;
            }


            break;
        }

        case TRANSFER_FILE_DATA:        // 接收到传输文件内容应答
        {
            (*recv_data_type) = TRANSFER_FILE_DATA;        // 记录下发有效数据类型

            // 传输文件内容应答正确
            if (RECV_OK == t_file_transfer.user_data[0])
            {
                // printf("file_data ok\n");
            }
            else
            {
                return OTHER_ERR;
            }

            break;
        }

        case TRANSFER_FILE_END:         // 接收到的是传输文件结束消息
        {
            (*recv_data_type) = TRANSFER_FILE_END;        // 记录下发有效数据类型

            // 服务器接收文件大小正确
            if (TRANSFER_OK == t_file_transfer.user_data[0])
            {
                return RECV_OK;
            }
            else        // 服务器接收文件大小错误
            {
                return TRANSFER_FAIL;
            }
        }

        default:
            break;
    }

    return RECV_OK;
}

/************************************************************************
函数名称: send_data_package
函数功能: 发送数据打包
函数参数: send_data_type: 发送数据类型
          send_user_data: 需要发送的用户数据
          send_user_data_len: 需要发送的用户数据长度
          send_data: 打包完成的数据
          send_data_len: 打包完成的数据长度
函数返回: 无
************************************************************************/
void main_widget::send_data_package(const unsigned char send_data_type,
                const char *send_user_data, const unsigned short send_user_data_len,
                char *send_data, unsigned long *send_data_len)
{
    unsigned short calc_crc = 0;
    struct file_transfer t_file_transfer;

    memset(&t_file_transfer, 0, sizeof(struct file_transfer));

    t_file_transfer.frame_head = CLIENT_HEAD;       // 帧头
    t_file_transfer.frame_number = main_widget::g_frame_number++;        // 帧序号
    t_file_transfer.user_data_type = send_data_type;        // 数据类型
    (*send_data_len) = 3;       // 计算帧头、帧序号、数据类型的长度

    // 填充用户数据部分
    t_file_transfer.user_data_len = send_user_data_len;      // 用户数据长度
    if (send_user_data_len > 0)
    {
        memcpy(t_file_transfer.user_data, send_user_data, send_user_data_len);
    }
    (*send_data_len) += (send_user_data_len + 2);

    memcpy(send_data, &t_file_transfer, (*send_data_len));

    calc_crc = calc_crc16((unsigned char *)send_data, (*send_data_len));     // 计算crc

    send_data[(*send_data_len)++] = (calc_crc & 0x00FF);
    send_data[(*send_data_len)++] = (calc_crc & 0xFF00) >> 8;
}

/************************************************************************
函数名称: file_transfer_function
函数功能: 文件传输函数
函数参数: 无
函数返回: 无
************************************************************************/
int main_widget::file_transfer_function(void)
{
    qint64 ret = -1;           // 函数返回值
    long recv_data_len = -1;       // 接收到的数据长度
    unsigned char recv_data_type = 0;       // 接收到的有效数据类型
    unsigned long send_data_len = 0;        // 发送数据长度
    char recv_data[MAX_USER_DATA_LEN + 7] = {0};    // 接收数据
    char send_data[MAX_USER_DATA_LEN + 7] = {0};    // 发送数据
    char read_data[MAX_USER_DATA_LEN] = {0};        // 从文件中读取到的内容

    qDebug() << "This is file_transfer_function";

    while(1)
    {
        // 还未发送文件名到服务器
        if ((0 == send_file_name_flag) && (0 == send_file_size_flag))
        {
            qDebug() << "send file_name";
            // 发送文件名数据打包
            memset(send_data, 0, sizeof(send_data));
            send_data_package(TRANSFER_FILE_NAME,
                              t_file_info.file_name,
                              strlen(t_file_info.file_name), send_data, &send_data_len);
        }
        // 文件名已发送, 但还未发送文件大小到服务器
        else if ((1 == send_file_name_flag) && (0 == send_file_size_flag))
        {
            // 发送文件大小数据打包
            memset(send_data, 0, sizeof(send_data));
            send_data_package(TRANSFER_FILE_SIZE,
                              (const char *)&t_file_info.file_size,
                              4, send_data, &send_data_len);
        }
        // 发送文件内容
        else if ((1 == send_file_name_flag) && (1 == send_file_size_flag) &&
                 (0 == send_file_data_flag))
        {
            // 读取文件内容到read_data
            memset(read_data, 0, sizeof(read_data));
//            ret = read_file(t_file_info.file_name, read_data_position,
//                            MAX_USER_DATA_LEN, read_data);
            ret = read_file(file_path, read_data_position,
                            MAX_USER_DATA_LEN, read_data);

            if (-1 != ret)
            {
                read_data_position += ret;
            }

            // 发送文件内容数据打包
            memset(send_data, 0, sizeof(send_data));
            send_data_package(TRANSFER_FILE_DATA, read_data, ret,
                              send_data, &send_data_len);
        }
        else        // 发送传输完成消息
        {
            // 发送传输完成消息打包
            memset(send_data, 0, sizeof(send_data));
            send_data_package(TRANSFER_FILE_END, NULL, 0, send_data, &send_data_len);
        }

        if (send_data_len > 0)      // 有数据需要发送到服务器
        {
            // 向服务器发送数据
            ret = tcp_client->write(send_data, send_data_len);
            if (-1 != ret)
            {
                send_data_len = 0;
                memset(send_data, 0, sizeof(send_data));
            }
            tcp_client->flush();        // 把缓冲的数据立刻发送出去
            tcp_client->waitForReadyRead();
        }

        // 接受服务器发送过来的数据
        memset(recv_data, 0, sizeof(recv_data));
        recv_data_len = tcp_client->read((char *)recv_data, sizeof(recv_data));
        if (recv_data_len > 0)        // 接收到数据
        {
            ret = recv_data_parsing((unsigned char *)recv_data, recv_data_len, &recv_data_type);
            if (RECV_OK == ret)     // 接收数据解析成功
            {
                // 发送文件名成功, 将标志位置1
                if (TRANSFER_FILE_NAME == recv_data_type)
                {
                    qDebug("send file name ok");

                    send_file_name_flag = 1;
                }
                // 发送文件大小成功, 将标志位置1
                else if (TRANSFER_FILE_SIZE == recv_data_type)
                {
                    qDebug("send file size ok");

                    send_file_size_flag = 1;
                }
                // 发送文件内容
                else if (TRANSFER_FILE_DATA == recv_data_type)
                {
                    // 记录完成百分比(保留整数)
                    int progress = ((float)read_data_position / t_file_info.file_size) * 100;
                    // display_progress(progress);
                    ui->progressBar->setValue(progress);

                    // 文件内容发送完成, 将标志置1
                    if (t_file_info.file_size == read_data_position)
                    {
                        qDebug("send file data end");

                        send_file_data_flag = 1;
                    }
                }
                // 接收到的是发送文件内容结束标志
                else if (TRANSFER_FILE_END == recv_data_type)
                {
                    // 打印传输成功信息
                    qDebug("file transfer successful");

                    return 0;
                }
            }
            else if (HEAD_ERR)      // 接收数据帧头错误
            {
                qDebug("file transfer error: recv head error");

                return HEAD_ERR;
            }
            else if (CRC_ERR)       // 接收数据CRC错误
            {
                qDebug("file transfer error: recv crc error");

                return CRC_ERR;
            }
            else if (TRANSFER_FAIL)     // 服务器接收文件大小错误
            {
                qDebug("file transfer fail");

                return TRANSFER_FAIL;
            }
            else        // 其它错误
            {
                qDebug("file transfer error: other error");

                return OTHER_ERR;
            }
        }
        else if (recv_data_len == 0)   // socket 正常关闭
        {
            qDebug("server socket closed!");

            return -1;      // 服务器关闭, 退出函数, 重连服务器
        }
        else if (recv_data_len == -1)  // 非阻塞模式下表示缓冲区空 if(ret == -1)
        {
        }
        else
        {
            // 错误处理
            if (EAGAIN != errno)
            {
                qDebug("recv error ret = %d", ret);

                return -1;      // 异常情况, 退出函数, 重连服务器
            }
        }
    }
}

// 打开链接按钮
void main_widget::on_link_pushButton_toggled(bool checked)
{
    if (true == checked)        // 按钮被按下
    {
        // 打开链接
        tcp_client->connectToHost(ui->ipLineEdit->text(),
                                 ui->portLineEdit->text().toUShort());

        if (tcp_client->waitForConnected(1000))     // 打开链接成功
        {
            QMessageBox::information(this, tr("打开链接"), tr("打开链接成功"),
                                               QMessageBox::Ok);

            ui->link_pushButton->setText(tr("关闭链接"));
        }
        else    // 打开链接失败
        {
            QMessageBox::information(this, tr("打开链接失败"), tcp_client->errorString(),
                                               QMessageBox::Ok);
        }
    }
    else        // 按钮未被按下
    {
        // 关闭链接
        tcp_client->disconnectFromHost();
        tcp_client->waitForDisconnected();

        ui->link_pushButton->setText(tr("打开链接"));
    }
}

// 选择文件按钮
void main_widget::on_choose_pushButton_clicked()
{
    file_path = QFileDialog::getOpenFileName(this);
    if (!file_path.isEmpty())
    {
        memset(&t_file_info, 0, sizeof(struct file_info));
        qDebug() << "file_path: " << file_path;

        // 将文件路径显示到文件框中
        ui->fileLineEdit->setText(file_path);

        // 获取文件名
        QByteArray ba = QFileInfo(file_path).fileName().toLatin1();
        memcpy(t_file_info.file_name, ba.data(), strlen(ba.data()));

        // 获取文件名和文件大小
        t_file_info.file_size = QFileInfo(file_path).size();

        qDebug("file_name: [%s]", t_file_info.file_name);
        qDebug() << "file_size: " << t_file_info.file_size;
    }
}

// 开始按钮
void main_widget::on_start_pushButton_clicked()
{
    int ret = -1;

    // 每次开始之前将进度条清零
    ui->progressBar->setValue(0);

    // 变量/标志位清零
    send_file_name_flag = 0;   // 发送文件名成功标志位
    send_file_size_flag = 0;   // 发送文件大小成功标志位
    send_file_data_flag = 0;   // 发送文件内容结束标志位
    read_data_position = 0;    // 记录读取文件内容位置

    // 判断是否连接到服务器
    if (!tcp_client->waitForConnected())
    {
        QMessageBox::information(this, tr("文件传输"), tr("未连接到服务器"),
                                           QMessageBox::Ok);
        return;
    }

    // 判断是否打开文件
    if (file_path.isEmpty())
    {
        QMessageBox::information(this, tr("文件传输"), tr("未选择传输文件"),
                                           QMessageBox::Ok);
        return;
    }

    ret = file_transfer_function();     // 启动文件传输
    if (0 == ret)       // 传输成功
    {
        QMessageBox::information(this, tr("文件传输"), tr("文件传输成功"),
                                           QMessageBox::Ok);

        // 传输完成, 关闭链接
        tcp_client->disconnectFromHost();
        tcp_client->waitForDisconnected();

        // 关闭链接后还需要更新按钮状态
        ui->link_pushButton->setChecked(false);
        ui->link_pushButton->setText(tr("打开链接"));

        return;
    }
    else if (-1 == ret)     // 服务器关闭/socket异常情况, 重连服务器
    {
        QMessageBox::information(this, tr("文件传输"), tr("服务器socket异常"),
                                           QMessageBox::Ok);

        // 关闭链接
        tcp_client->disconnectFromHost();
        tcp_client->waitForDisconnected();

        return;
    }
    else        // 传输过程发生错误, 结束传输
    {
        QMessageBox::information(this, tr("文件传输"), tr("文件传输失败"),
                                           QMessageBox::Ok);

        // 关闭链接
        tcp_client->disconnectFromHost();
        tcp_client->waitForDisconnected();

        return;
    }
}
